package at.fhooe.stefanirndorfer.labeling;

import static at.fhooe.stefanirndorfer.utilities.Utilities.E;
import static at.fhooe.stefanirndorfer.utilities.Utilities.N;
import static at.fhooe.stefanirndorfer.utilities.Utilities.NE;
import static at.fhooe.stefanirndorfer.utilities.Utilities.NW;
import static at.fhooe.stefanirndorfer.utilities.Utilities.S;
import static at.fhooe.stefanirndorfer.utilities.Utilities.SE;
import static at.fhooe.stefanirndorfer.utilities.Utilities.SW;
import static at.fhooe.stefanirndorfer.utilities.Utilities.W;

import java.util.ArrayList;
import java.util.Iterator;

import at.fhooe.stefanirndorfer.logging.Log;
import at.fhooe.stefanirndorfer.quadtree.QuadTreeNode;


public class Labeling {
	
	
	QuadTreeNode rootNode;
	ArrayList<Region[]> merges;
	
	public Labeling(QuadTreeNode rootNode){
		this.rootNode = rootNode;
		merges = new ArrayList<Region[]>();
		label(rootNode);
	}
	
	/**
	 * assigns labels to node p and its sons
	 */
	private void label(QuadTreeNode p){
		// if is no leaf
		if(p.getChildNodes()[NW] != null){
			
			// label in order NW, NE, SW, SE
			label(p.getChildNodes()[NW]);
			label(p.getChildNodes()[NE]);
			label(p.getChildNodes()[SW]);
			label(p.getChildNodes()[SE]);
		} else {
			// if is a room leaf
			if(!p.isWall()){
				QuadTreeNode q = get_Equal_Adj_Neighbour(p, E);
				if(q != null){
					label_Adjacent(q, NW, SW, p);					
					q = get_Equal_Adj_Neighbour(p, S);
				}
				
				if(q != null){
					label_Adjacent(q, NW, NE, p);			
				}
				
				if(!p.isLabeled()){
					assign_New_Label(p);
				}
			}
			// else its a wall leaf
		}
	}
	

	/**
	 * @param p
	 */
	private void assign_New_Label(QuadTreeNode p) {
		Region r = new Region();
		p.setLabel(r);
		r.addLeaf(p);
	}
	
	/**
	 * Finds all descendants of node R adjacent to node P--1 e, in quadrants Q1 and Q2
	 * @param r
	 * @param q1
	 * @param q2
	 * @param p
	 */
	private void label_Adjacent(QuadTreeNode r, short q1, short q2,
			QuadTreeNode p) {
		if(r.getChildNodes()[0] != null){
			label_Adjacent(r.getChildNodes()[q1], q1, q2, p);
			label_Adjacent(r.getChildNodes()[q2], q1, q2, p);
		}else{
			if(!r.isWall()){
				assign_Label(p,r);
			}
			//else would be a wall leaf
		}
		
	}

	/**
	 * Assign a label to nodes p and q if they do not 
	 * already have one. If both have different labels, then enter them in MERGES
	 * @param p
	 * @param r
	 */
	private void assign_Label(QuadTreeNode p, QuadTreeNode q) {
		if(p.isLabeled() && q.isLabeled()){
			if(!p.getLabel().getId().equals(q.getLabel().getId())){
				merges.add(new Region[]{p.getLabel(), q.getLabel()});
				Log.info(this, "Length of merge-array: " + merges.size());
			}
		}else{
			if(p.isLabeled()){
				q.setLabel(p.getLabel());
				q.getLabel().addLeaf(q);
			}else{
				if(q.isLabeled()){
					p.setLabel(q.getLabel());
					p.getLabel().addLeaf(p);
				}else{
					assign_New_Label(p);
					q.setLabel(p.getLabel());
					q.getLabel().addLeaf(q);
				}
			} 
		}
	}

	/**
	 * Returns the neighbor of node P in horizontal or vertical direction D 
	 * which is greater than or equal in size to P. If such a node does 
	 * not exist, then a GRAY node of equal size is returned If this is also impossible, 
	 * then the node is adjacent to the border of the image and NULL is returned
	 * @param p
	 * @param direction
	 * @return
	 */
	private QuadTreeNode get_Equal_Adj_Neighbour(QuadTreeNode p, short direction) {
		QuadTreeNode q = null;
		
		if(p.getFather() != null && adj(direction, p.getSontype())){
				/* find a common ancestor */
				q = get_Equal_Adj_Neighbour(p.getFather(), direction);
		} else {
			q = p.getFather();
		}
		
		/* follow the reflected path back to locate the neighbor */
		if(q != null && q.getChildNodes()[0] != null){
			q = q.getChildNodes()[reflect(direction, p.getSontype())];
		}
		return q;
	}

	private void cleanUp_Merges(){
		Iterator<Region[]> it = merges.iterator();
		
		while(it.hasNext()){
			Region[] currRegs = it.next();
			Region currSrcReg = currRegs[0];
			Region currDestReg = currRegs[1];
			currSrcReg.moveRegionTo(currDestReg);
			
			it.remove();
			Log.info(this, "merges length: " + merges.size());
		}
	}

	
	private synchronized void mergeRegion(Region label){
		
		ArrayList<Region> sameRegion = new ArrayList<Region>();
		Iterator<Region[]> it = merges.iterator();
		
		while(it.hasNext()){
			Region[] currElem = it.next();
			if (currElem[0].getId().equals(label.getId())) {
				sameRegion.add(currElem[1]);
				it.remove();
				mergeRegion(currElem[1]);
			}else{
				if (currElem[1].getId().equals(label.getId())) {
					sameRegion.add(currElem[0]);
					it.remove();
					mergeRegion(currElem[0]);
				}
			}
		}
		
		Iterator<Region> sameRegIt = sameRegion.iterator();
		while(sameRegIt.hasNext()){
			Region currReg = sameRegIt.next();
			moveLabelsToOtherRegion(label, currReg);
			it.remove();
		}
	}
	
	
//	private void mergeRegion(Region label) {
//		Iterator<Region[]> it = merges.iterator();
//		while (it.hasNext()) {
//			Region[] currElem = it.next();
//			if (currElem[0].getId().equals(label.getId())) {
//				Region regToMerge = currElem[1];
//				moveLabelsToOtherRegion(label, regToMerge);
//				it.remove();
//				mergeRegion(regToMerge);
//				Log.info(this, "Merges: one removed, new size: " + merges.size());
//			} else {
//				if (currElem[1].getId().equals(label.getId())) {
//					Log.info(this, "guckguck");
//					Region regToMerge = currElem[0];
//					moveLabelsToOtherRegion(label, regToMerge);
//					it.remove();
//					mergeRegion(regToMerge);
//					Log.info(this, "Merges: one removed, new size: " + merges.size());
//				}
//			}
//
//			Log.info(this, "Length of merge-array: " + merges.size());
//		}
//	}

	/**
	 * @param label
	 * @param regToMerge
	 */
	private synchronized void moveLabelsToOtherRegion(Region label, Region regToMerge) {
		ArrayList<QuadTreeNode> toMergeLeafs = regToMerge
				.getLeafs();

		Iterator<QuadTreeNode> mergeIt = toMergeLeafs.iterator();
		while (mergeIt.hasNext()) {
			mergeIt.next().setLabel(label);
			mergeIt.remove();
		}
	}

	/**
	 * returns the quadrant (NW, NE ...) of a given quadrant mirrored over axis of param dir
	 * @param dir
	 * @param quadrant
	 * @return
	 */
	private short reflect(short dir, short quadrant){
		switch(dir){
		case (N):
			switch(quadrant){
			case NW:
				return SW; 
			case NE:
				return SE;
			case SE:
				return NE;
			case SW:
				return NW;
			}
			break;
			
		case E:
			switch(quadrant){
			case NW:
				return NE;
			case NE:
				return NW;
			case SE:
				return SW;
			case SW:
				return SE;
			}
			break;
		case S:
			switch(quadrant){
			case NW:
				return SW;
			case NE:
				return SE;
			case SE:
				return NE;
			case SW:
				return NW;
			}
			break;
			
		case W:
			switch(quadrant){
			case NW:
				return NE;
			case NE:
				return NW;
			case SE:
				return SW;
			case SW:
				return SE;
			}
			break;
		}
		// this should not be reached
		Log.error(this, "something wrong with the reflect-method");
		return -1;
	}
	
	/**
	 * ADJ(B, I) is true if and only if 
	 * childnode (quadrant) I is adjacent to side B of a node
	 * @param dir
	 * @param quadrant
	 * @return
	 */
	private boolean adj(short dir, short quadrant){
		
		switch(dir){
		case (N):
			switch(quadrant){
			case NW:
				return true; 
			case NE:
				return true;
			case SE:
				return false;
			case SW:
				return false;
			}
			break;
			
		case E:
			switch(quadrant){
			case NW:
				return false;
			case NE:
				return true;
			case SE:
				return true;
			case SW:
				return false;
			}
			break;
		case S:
			switch(quadrant){
			case NW:
				return false;
			case NE:
				return false;
			case SE:
				return true;
			case SW:
				return true;
			}
			break;
			
		case W:
			switch(quadrant){
			case NW:
				return true;
			case NE:
				return false;
			case SE:
				return false;
			case SW:
				return true;
			}
			break;
		}
		// this should not be reached
		Log.error(this, "something wrong with the adj-method");
		return false;
	}

	public void merge() {
		if(merges.size() >= 0){
			cleanUp_Merges();
		}else{
			Log.info(this, "nothing to merge!");
		}
	}

}
